﻿using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.VisualBasic.Devices;
using System.IO.MemoryMappedFiles;

namespace Sort
{
    class Sort
    {
        private static long splitNum, totalCount;
        private static string tempPath;
        private static ulong offset;
        private static ulong fileSize;
        private static ulong bufferSize;

        private static ulong FreeMemorySize
        {
            get
            {
                ComputerInfo ci = new ComputerInfo();
                return ci.AvailablePhysicalMemory;
            }
        }

        public static void InputProcessing(string inputFileName, string outputFileName)
        {
            File.Delete(outputFileName);
            FileStream output = File.OpenWrite(outputFileName);
            output.Close();
            Random rnd = new Random();
            tempPath = Path.Combine(Path.GetTempPath(), rnd.Next().ToString());
            Directory.CreateDirectory(tempPath);
            FileInfo input = new FileInfo(inputFileName);
            if (!input.Exists)
                throw new ArgumentException("Input file not found");
            fileSize = (ulong)input.Length;
            if (fileSize == 0)
                throw new ArgumentException("Input file is empty");
            bufferSize = (ulong)(FreeMemorySize * 0.5);
            SplitInput(inputFileName);
            ChunksMerging(outputFileName);
        }

        static void ChunkProcessing(StreamReader inputStream, string outputFileName)
        {
            if (inputStream == null)
            {
                return;
            }
            DateTime start = DateTime.Now;

            List<uint> inputArray = new List<uint>();
            uint num = 0;

            long position = 0;

            //pushing elements to array
            int j = 0;
            while (!inputStream.EndOfStream)
            {
                if (inputStream.Peek() >= '0' && inputStream.Peek() <= '9')
                {
                    if (num * 10 < num)
                    {
                        uint temp = num;
                        int counter = 0;
                        while (temp > 9)
                        {
                            temp /= 10;
                            counter++;
                        }
                        while (counter-- > 0)
                            temp *= 10;
                        num -= temp;
                    }
                    num *= 10;
                    num += (uint)(char)inputStream.Read() - '0';
                }
                else if (inputStream.Read() == ' ')
                {
                    if (num != 0)
                        inputArray.Add(num);
                    num = 0;
                    position = j;
                }
                j++;
            }

            if (offset == fileSize)
            {
                if (num != 0)
                    inputArray.Add(num);
            }
            else
            {
                offset -= (ulong)(j - position);
            }

            totalCount += inputArray.Count;

            inputArray.Sort();

            StreamWriter outputWriter = new StreamWriter(outputFileName);
            for (int i = 0; i < inputArray.Count; i++)
            {
                outputWriter.WriteLine(inputArray[i]);
            }
            outputWriter.Close();
            DateTime end = DateTime.Now;
            Program.logger.Info("Sort - " + (end - start));
        }

        static void SplitInput(string inputFileName)
        {
            splitNum = 0;

            offset = 0;

            while (offset < fileSize)
            {
                if (fileSize - offset <= bufferSize)
                {
                    bufferSize = fileSize - offset;
                }
                MemoryMappedFile mf = MemoryMappedFile.CreateFromFile(inputFileName, FileMode.Open);
                MemoryMappedViewStream ms = mf.CreateViewStream((long)offset, (long)bufferSize);
                offset += bufferSize;

                StreamReader sr = new StreamReader(ms);

                ChunkProcessing(sr, Path.Combine(tempPath, string.Format("sorted{0:d5}.dat", splitNum)));

                sr.Close();
                ms.Close();
                mf.Dispose();

                Console.Clear();
                Program.logger.Info((splitNum + 1) + " file(s) sorted");
                splitNum++;
            }
        }

        static void ChunksMerging(string outputFileName)
        {
            string[] paths = Directory.GetFiles(tempPath, "sorted*.dat");
            int size = paths.Length;
            int bufferLen = (int)((FreeMemorySize * 0.5) / size / 10);

            StreamReader[] fileStreams = new StreamReader[size];
            StreamWriter output = new StreamWriter(outputFileName);
            string lowestValue = "";
            int lowestIndex = 0;
            Queue<string>[] queues = new Queue<string>[size];
            for (int i = 0; i < size; i++)
            {
                fileStreams[i] = new StreamReader(paths[i]);
                queues[i] = new Queue<string>(bufferLen);
                QueueLoading(queues[i], fileStreams[i], bufferLen);
            }

            bool done = false;
            while (!done)
            {
                lowestIndex = -1;
                for (int i = 0; i < size; i++)
                {
                    if (queues[i] != null)
                    {
                        if (lowestIndex < 0 || Convert.ToUInt32(queues[i].Peek()) <= Convert.ToUInt32(lowestValue))
                        {
                            lowestValue = queues[i].Peek();
                            lowestIndex = i;
                        }
                    }
                }

                if (lowestIndex == -1)
                {
                    done = true;
                    break;
                }

                output.Write(lowestValue);
                if (--totalCount > 0)
                    output.Write(" ");

                queues[lowestIndex].Dequeue();

                if (queues[lowestIndex].Count == 0)
                {
                    QueueLoading(queues[lowestIndex], fileStreams[lowestIndex], bufferLen);
                    if (queues[lowestIndex].Count == 0)
                    {
                        queues[lowestIndex] = null;
                    }
                }
            }
            for (int i = 0; i < paths.Length; i++)
            {
                fileStreams[i].Close();
                File.Delete(paths[i]);
            }
            Directory.Delete(tempPath);
            output.Close();
        }

        static void QueueLoading(Queue<string> queue, StreamReader file, int records)
        {
            for (int i = 0; i < records; i++)
            {
                if (file.Peek() < 0) break;
                queue.Enqueue(file.ReadLine());
            }
        }
    }
}
